home *** CD-ROM | disk | FTP | other *** search
Wrap
# Source Generated with Decompyle++ # File: in.pyc (Python 2.6) '''Provides the default implementation for flat review for Orca.''' __id__ = '$Id: flat_review.py 4471 2009-01-28 17:52:43Z wwalker $' __version__ = '$Revision: 4471 $' __date__ = '$Date: 2009-01-28 12:52:43 -0500 (Wed, 28 Jan 2009) $' __copyright__ = 'Copyright (c) 2005-2008 Sun Microsystems Inc.' __license__ = 'LGPL' import re import pyatspi import braille import debug import eventsynthesizer import orca_state import rolenames import settings from orca_i18n import _ from orca_i18n import C_ whitespace_re = re.compile('(\\s+)', re.DOTALL | re.IGNORECASE | re.M) class Char: '''Represents a single char of an Accessibility_Text object.''' def __init__(self, word, index, string, x, y, width, height): '''Creates a new char. Arguments: - word: the Word instance this belongs to - index: the index of this char in the word - string: the actual char - x, y, width, height: the extents of this Char on the screen ''' self.word = word self.string = string self.index = index self.x = x self.y = y self.width = width self.height = height class Word: '''Represents a single word of an Accessibility_Text object, or the entire name of an Image or Component if the associated object does not implement the Accessibility_Text interface. As a rule of thumb, all words derived from an Accessibility_Text interface will start with the word and will end with all chars up to the beginning of the next word. That is, whitespace and punctuation will usually be tacked on to the end of words.''' def __init__(self, zone, index, startOffset, string, x, y, width, height): '''Creates a new Word. Arguments: - zone: the Zone instance this belongs to - index: the index of this word in the Zone - string: the actual string - x, y, width, height: the extents of this Char on the screen''' self.zone = zone self.index = index self.startOffset = startOffset self.string = string self.length = len(string.decode('UTF-8')) self.x = x self.y = y self.width = width self.height = height def __getattr__(self, attr): '''Used for lazily determining the chars of a word. We do this to reduce the total number of round trip calls to the app, and to also spread the round trip calls out over the lifetime of a flat review context. Arguments: - attr: a string indicating the attribute name to retrieve Returns the value of the given attribute. ''' if attr == 'chars': if isinstance(self.zone, TextZone): text = self.zone.accessible.queryText() self.chars = [] i = 0 while i < self.length: (char, startOffset, endOffset) = text.getTextAtOffset(self.startOffset + i, pyatspi.TEXT_BOUNDARY_CHAR) if len(char): char = char.decode('UTF-8')[0].encode('UTF-8') (x, y, width, height) = text.getRangeExtents(startOffset, startOffset + 1, 0) self.chars.append(Char(self, i, char, x, y, width, height)) i += 1 else: self.chars = None return self.chars if attr.startswith('__') and attr.endswith('__'): raise AttributeError, attr attr.endswith('__') return self.__dict__[attr] class Zone: '''Represents text that is a portion of a single horizontal line.''' def __init__(self, accessible, string, x, y, width, height, role = None): """Creates a new Zone, which is a horizontal region of text. Arguments: - accessible: the Accessible associated with this Zone - string: the string being displayed for this Zone - extents: x, y, width, height in screen coordinates - role: Role to override accesible's role. """ self.accessible = accessible self.string = string self.length = len(string.decode('UTF-8')) self.x = x self.y = y self.width = width self.height = height if not role: pass self.role = accessible.getRole() def __getattr__(self, attr): '''Used for lazily determining the words in a Zone. Arguments: - attr: a string indicating the attribute name to retrieve Returns the value of the given attribute. ''' if attr == 'words': self.words = [] return self.words if attr.startswith('__') and attr.endswith('__'): raise AttributeError, attr attr.endswith('__') return self.__dict__[attr] def onSameLine(self, zone): '''Returns True if this Zone is on the same horiztonal line as the given zone.''' highestBottom = min(self.y + self.height, zone.y + zone.height) lowestTop = max(self.y, zone.y) if lowestTop < highestBottom: overlapAmount = highestBottom - lowestTop shortestHeight = min(self.height, zone.height) return 1 * overlapAmount / shortestHeight > 0.25 return False def getWordAtOffset(self, charOffset): wordAtOffset = None offset = 0 for word in self.words: nextOffset = offset + len(word.string.decode('UTF-8')) wordAtOffset = word if nextOffset > charOffset: return [ wordAtOffset, charOffset - offset] offset = nextOffset return [ wordAtOffset, offset] class TextZone(Zone): '''Represents Accessibility_Text that is a portion of a single horizontal line.''' def __init__(self, accessible, startOffset, string, x, y, width, height): '''Creates a new Zone, which is a horizontal region of text. Arguments: - accessible: the Accessible associated with this Zone - startOffset: the index of the char in the Accessibility_Text interface where this Zone starts - string: the string being displayed for this Zone - extents: x, y, width, height in screen coordinates ''' Zone.__init__(self, accessible, string, x, y, width, height) self.startOffset = startOffset def __getattr__(self, attr): '''Used for lazily determining the words in a Zone. The words will either be all whitespace (interword boundaries) or actual words. To determine if a Word is whitespace, use word.string.isspace() Arguments: - attr: a string indicating the attribute name to retrieve Returns the value of the given attribute. ''' if attr == 'words': text = self.accessible.queryText() self.words = [] wordIndex = 0 offset = self.startOffset for string in whitespace_re.split(self.string): if len(string): endOffset = offset + len(string.decode('UTF-8')) (x, y, width, height) = text.getRangeExtents(offset, endOffset, 0) word = Word(self, wordIndex, offset, string, x, y, width, height) self.words.append(word) wordIndex += 1 offset = endOffset continue return self.words if attr.startswith('__') and attr.endswith('__'): raise AttributeError, attr attr.endswith('__') return self.__dict__[attr] class StateZone(Zone): '''Represents a Zone for an accessible that shows a state using a graphical indicator, such as a checkbox or radio button.''' def __init__(self, accessible, x, y, width, height, role = None): Zone.__init__(self, accessible, '', x, y, width, height, role) del self.string def __getattr__(self, attr): if attr in ('string', 'length', 'brailleString'): stateset = self.accessible.getState() if stateset.contains(pyatspi.STATE_INDETERMINATE): stateCount = 2 elif stateset.contains(pyatspi.STATE_CHECKED): stateCount = 1 else: stateCount = 0 if self.role in [ pyatspi.ROLE_CHECK_BOX, pyatspi.ROLE_CHECK_MENU_ITEM, pyatspi.ROLE_TABLE_CELL]: if stateCount == 2: speechState = _('partially checked') elif stateCount == 1: speechState = _('checked') else: speechState = _('not checked') brailleState = settings.brailleCheckBoxIndicators[stateCount] elif self.role == pyatspi.ROLE_TOGGLE_BUTTON: if stateCount: speechState = _('pressed') else: speechState = _('not pressed') brailleState = settings.brailleRadioButtonIndicators[stateCount] elif stateCount: speechState = C_('radiobutton', 'selected') else: speechState = C_('radiobutton', 'not selected') brailleState = settings.brailleRadioButtonIndicators[stateCount] if attr == 'string': return speechState if attr == 'length': return len(speechState) if attr == 'brailleString': return brailleState else: return Zone.__getattr__(self, attr) return attr == 'brailleString' class ValueZone(Zone): '''Represents a Zone for an accessible that shows a value using a graphical indicator, such as a progress bar or slider.''' def __init__(self, accessible, x, y, width, height, role = None): Zone.__init__(self, accessible, '', x, y, width, height, role) del self.string def __getattr__(self, attr): if attr in ('string', 'length', 'brailleString'): orientation = None if self.role in [ pyatspi.ROLE_SLIDER, pyatspi.ROLE_SCROLL_BAR]: stateset = self.accessible.getState() if stateset.contains(pyatspi.STATE_HORIZONTAL): orientation = _('horizontal') elif stateset.contains(pyatspi.STATE_VERTICAL): orientation = _('vertical') value = self.accessible.queryValue() percentValue = int((value.currentValue / (value.maximumValue - value.minimumValue)) * 100) if orientation: speechValue = orientation + ' ' + rolenames.getSpeechForRoleName(self.accessible) else: speechValue = rolenames.getSpeechForRoleName(self.accessible) speechValue = speechValue + ' ' + _('%d percent.') % percentValue if orientation: brailleValue = '%s %s %d%%' % (orientation, rolenames.getBrailleForRoleName(self.accessible), percentValue) else: brailleValue = '%s %d%%' % (rolenames.getBrailleForRoleName(self.accessible), percentValue) if attr == 'string': return speechValue if attr == 'length': return len(speechValue) if attr == 'brailleString': return brailleValue else: return Zone.__getattr__(self, attr) return attr == 'brailleString' class Line: '''A Line is a single line across a window and is composed of Zones.''' def __init__(self, index, zones): '''Creates a new Line, which is a horizontal region of text. Arguments: - index: the index of this Line in the window - zones: the Zones that make up this line ''' self.index = index self.zones = zones self.brailleRegions = None def __getattr__(self, attr): if attr in ('string', 'length', 'x', 'y', 'width', 'height'): bounds = None string = '' for zone in self.zones: if not bounds: bounds = [ zone.x, zone.y, zone.x + zone.width, zone.y + zone.height] else: bounds[0] = min(bounds[0], zone.x) bounds[1] = min(bounds[1], zone.y) bounds[2] = max(bounds[2], zone.x + zone.width) bounds[3] = max(bounds[3], zone.y + zone.height) if len(zone.string): if len(string): string += ' ' string += zone.string continue if not bounds: bounds = [ -1, -1, -1, -1] if attr == 'string': return string if attr == 'length': return len(string) if attr == 'x': return bounds[0] if attr == 'y': return bounds[1] if attr == 'width': return bounds[2] - bounds[0] if attr == 'height': return bounds[3] - bounds[1] elif attr.startswith('__') and attr.endswith('__'): raise AttributeError, attr else: return self.__dict__[attr] return attr == 'width' def getBrailleRegions(self): if True or not (self.brailleRegions): self.brailleRegions = [] brailleOffset = 0 for zone in self.zones: if isinstance(zone, TextZone): if zone.accessible.getRole() in (pyatspi.ROLE_TEXT, pyatspi.ROLE_PASSWORD_TEXT, pyatspi.ROLE_TERMINAL) or zone.accessible.getRole() in (pyatspi.ROLE_PARAGRAPH, pyatspi.ROLE_HEADING, pyatspi.ROLE_LINK): region = braille.ReviewText(zone.accessible, zone.string, zone.startOffset, zone) else: try: brailleString = zone.brailleString except: brailleString = zone.string region = braille.ReviewComponent(zone.accessible, brailleString, 0, zone) if len(self.brailleRegions): pad = braille.Region(' ') pad.brailleOffset = brailleOffset self.brailleRegions.append(pad) brailleOffset += 1 zone.brailleRegion = region region.brailleOffset = brailleOffset self.brailleRegions.append(region) brailleOffset += len(region.string.decode('UTF-8')) if not settings.disableBrailleEOL: if len(self.brailleRegions): pad = braille.Region(' ') pad.brailleOffset = brailleOffset self.brailleRegions.append(pad) brailleOffset += 1 eol = braille.Region('$l') eol.brailleOffset = brailleOffset self.brailleRegions.append(eol) return self.brailleRegions class Context: '''Information regarding where a user happens to be exploring right now. ''' ZONE = 0 CHAR = 1 WORD = 2 LINE = 3 WINDOW = 4 WRAP_NONE = 0 WRAP_LINE = 1 WRAP_TOP_BOTTOM = 2 WRAP_ALL = WRAP_LINE | WRAP_TOP_BOTTOM def __init__(self, script): '''Create a new Context that will be used for handling flat review mode. ''' self.script = script if not (orca_state.locusOfFocus) or orca_state.locusOfFocus.getApplication() != self.script.app: self.lines = [] else: obj = orca_state.locusOfFocus while obj and obj.parent and obj.parent.getRole() != pyatspi.ROLE_APPLICATION and obj != obj.parent: obj = obj.parent if obj: self.lines = self.clusterZonesByLine(self.getShowingZones(obj)) else: self.lines = [] currentLineIndex = 0 currentZoneIndex = 0 currentWordIndex = 0 currentCharIndex = 0 if orca_state.locusOfFocus and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL: searchZone = orca_state.activeScript.getRealActiveDescendant(orca_state.locusOfFocus) else: searchZone = orca_state.locusOfFocus foundZoneWithFocus = False while currentLineIndex < len(self.lines): line = self.lines[currentLineIndex] currentZoneIndex = 0 while currentZoneIndex < len(line.zones): zone = line.zones[currentZoneIndex] if self.script.isSameObject(zone.accessible, searchZone): foundZoneWithFocus = True break continue currentZoneIndex += 1 if foundZoneWithFocus: break continue currentLineIndex += 1 if not foundZoneWithFocus: currentLineIndex = 0 currentZoneIndex = 0 elif isinstance(zone, TextZone): accessible = zone.accessible lineIndex = currentLineIndex zoneIndex = currentZoneIndex try: caretOffset = zone.accessible.queryText().caretOffset except NotImplementedError: caretOffset = -1 foundZoneWithCaret = False checkForEOF = False while lineIndex < len(self.lines): line = self.lines[lineIndex] while zoneIndex < len(line.zones): zone = line.zones[zoneIndex] if zone.accessible == accessible: if caretOffset >= zone.startOffset: if caretOffset < zone.startOffset + zone.length: foundZoneWithCaret = True break elif caretOffset == zone.startOffset + zone.length: checkForEOF = True lineToCheck = lineIndex zoneToCheck = zoneIndex zoneIndex += 1 if foundZoneWithCaret: currentLineIndex = lineIndex currentZoneIndex = zoneIndex currentWordIndex = 0 currentCharIndex = 0 offset = zone.startOffset while currentWordIndex < len(zone.words): word = zone.words[currentWordIndex] if word.length + offset > caretOffset: currentCharIndex = caretOffset - offset break continue currentWordIndex += 1 offset += word.length break continue zoneIndex = 0 lineIndex += 1 if not foundZoneWithCaret: pass atEOF = checkForEOF if atEOF: line = self.lines[lineToCheck] zone = line.zones[zoneToCheck] currentLineIndex = lineToCheck currentZoneIndex = zoneToCheck if caretOffset and zone.words: currentWordIndex = len(zone.words) - 1 currentCharIndex = zone.words[currentWordIndex].length - 1 self.lineIndex = currentLineIndex self.zoneIndex = currentZoneIndex self.wordIndex = currentWordIndex self.charIndex = currentCharIndex self.targetCharInfo = None def clip(self, ax, ay, awidth, aheight, bx, by, bwidth, bheight): """Clips region 'a' by region 'b' and returns the new region as a list: [x, y, width, height]. """ x = max(ax, bx) x2 = min(ax + awidth, bx + bwidth) width = x2 - x y = max(ay, by) y2 = min(ay + aheight, by + bheight) height = y2 - y return [ x, y, width, height] def splitTextIntoZones(self, accessible, string, startOffset, cliprect): """Traverses the string, splitting it up into separate zones if the string contains the EMBEDDED_OBJECT_CHARACTER, which is used by apps such as Firefox to handle containment of things such as links in paragraphs. Arguments: - accessible: the accessible - string: a substring from the accessible's text specialization - startOffset: the starting character offset of the string - cliprect: the extents that the Zones must fit inside. Returns a list of Zones for the visible text or None if nothing is visible. """ anyVisible = False zones = [] text = accessible.queryText() substringStartOffset = startOffset substringEndOffset = startOffset unicodeStartOffset = 0 unicodeString = string.decode('UTF-8') for i in range(0, len(unicodeString) + 1): if i != len(unicodeString) and unicodeString[i] != orca_state.activeScript.EMBEDDED_OBJECT_CHARACTER: substringEndOffset += 1 continue if substringEndOffset == substringStartOffset: substringStartOffset += 1 substringEndOffset = substringStartOffset unicodeStartOffset = i + 1 continue (x, y, width, height) = text.getRangeExtents(substringStartOffset, substringEndOffset, 0) if self.script.visible(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height): anyVisible = True clipping = self.clip(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) substring = unicodeString[unicodeStartOffset:i] zones.append(TextZone(accessible, substringStartOffset, substring.encode('UTF-8'), clipping[0], clipping[1], clipping[2], clipping[3])) substringStartOffset = substringEndOffset + 1 substringEndOffset = substringStartOffset unicodeStartOffset = i + 1 continue if anyVisible: return zones return None def getZonesFromText(self, accessible, cliprect): '''Gets a list of Zones from an object that implements the AccessibleText specialization. Arguments: - accessible: the accessible - cliprect: the extents that the Zones must fit inside. Returns a list of Zones. ''' debug.println(debug.LEVEL_FINEST, ' looking at text:') try: text = accessible.queryText() except NotImplementedError: return [] zones = [] offset = 0 lastEndOffset = -1 upperMax = lowerMax = text.characterCount upperMid = lowerMid = upperMax / 2 upperMin = lowerMin = 0 upperY = lowerY = 0 oldMid = 0 while oldMid != upperMid: oldMid = upperMid (x, y, width, height) = text.getRangeExtents(upperMid, upperMid + 1, 0) upperY = y if y > cliprect.y: upperMax = upperMid else: upperMin = upperMid upperMid = (upperMax - upperMin) / 2 + upperMin oldMid = 0 limit = cliprect.y + cliprect.height while oldMid != lowerMid: oldMid = lowerMid (x, y, width, height) = text.getRangeExtents(lowerMid, lowerMid + 1, 0) lowerY = y if y > limit: lowerMax = lowerMid else: lowerMin = lowerMid lowerMid = (lowerMax - lowerMin) / 2 + lowerMin offset = upperMin length = lowerMax while offset < length: (string, startOffset, endOffset) = text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_LINE_START) debug.println(debug.LEVEL_FINEST, " line at %d is (start=%d end=%d): '%s'" % (offset, startOffset, endOffset, string)) if startOffset < 0 and endOffset < 0 and startOffset > offset and endOffset < offset and startOffset > endOffset or abs(endOffset - startOffset) > 666000: debug.println(debug.LEVEL_WARNING, "flat_review:getZonesFromText detected garbage from getTextAtOffset for accessible name='%s' role'='%s': offset used=%d, start/end offset returned=(%d,%d), string='%s'" % (accessible.name, accessible.getRoleName(), offset, startOffset, endOffset, string)) break if endOffset == lastEndOffset: offset = max(offset + 1, lastEndOffset + 1) lastEndOffset = endOffset continue else: offset = endOffset lastEndOffset = endOffset textZones = self.splitTextIntoZones(accessible, string, startOffset, cliprect) if endOffset == length and string[-1:] == '\n': (x, y, width, height) = text.getRangeExtents(startOffset, endOffset, 0) if not textZones: textZones = [] textZones.append(TextZone(accessible, endOffset, '', x, y + height, 0, height)) if textZones: zones.extend(textZones) continue if len(zones): break continue if len(zones) == 0: if accessible.getRole() == pyatspi.ROLE_TEXT and accessible.getRole() == pyatspi.ROLE_ENTRY or accessible.getRole() == pyatspi.ROLE_PASSWORD_TEXT: extents = accessible.queryComponent().getExtents(0) zones.append(TextZone(accessible, 0, '', extents.x, extents.y, extents.width, extents.height)) return zones def _insertStateZone(self, zones, accessible, role = None): '''If the accessible presents non-textual state, such as a checkbox or radio button, insert a StateZone representing that state.''' zone = None stateOnLeft = True if not role: pass role = accessible.getRole() if role in [ pyatspi.ROLE_CHECK_BOX, pyatspi.ROLE_CHECK_MENU_ITEM, pyatspi.ROLE_RADIO_BUTTON, pyatspi.ROLE_RADIO_MENU_ITEM]: extents = accessible.queryComponent().getExtents(0) stateX = extents.x stateY = extents.y stateWidth = 1 stateHeight = extents.height try: text = accessible.queryText() except NotImplementedError: pass (x, y, width, height) = text.getRangeExtents(0, text.characterCount, 0) textToLeftEdge = x - extents.x textToRightEdge = extents.x + extents.width - (x + width) stateOnLeft = textToLeftEdge > 20 if stateOnLeft: stateWidth = textToLeftEdge else: stateX = x + width stateWidth = textToRightEdge zone = StateZone(accessible, stateX, stateY, stateWidth, stateHeight) elif role == pyatspi.ROLE_TOGGLE_BUTTON: extents = accessible.queryComponent().getExtents(0) zone = StateZone(accessible, extents.x, extents.y, 1, extents.height) elif role == pyatspi.ROLE_TABLE_CELL: try: action = accessible.queryAction() except NotImplementedError: action = None if action: hasToggle = False for i in range(0, action.nActions): if action.getName(i) in [ 'toggle', _('toggle')]: hasToggle = True break continue if hasToggle: self._insertStateZone(zones, accessible, pyatspi.ROLE_CHECK_BOX) if zone: if stateOnLeft: zones.insert(0, zone) else: zones.append(zone) def getZonesFromAccessible(self, accessible, cliprect): '''Returns a list of Zones for the given accessible. Arguments: - accessible: the accessible - cliprect: the extents that the Zones must fit inside. ''' icomponent = accessible.queryComponent() if not icomponent: return [] extents = icomponent.getExtents(0) if not self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height): return [] debug.println(debug.LEVEL_FINEST, 'flat_review.getZonesFromAccessible (name=%s role=%s)' % (accessible.name, accessible.getRoleName())) try: accessible.queryText() except NotImplementedError: self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) icomponent zones = [] except: self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) zones = self.getZonesFromText(accessible, cliprect) try: iimage = accessible.queryImage() except NotImplementedError: self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) icomponent iimage = None except: self.script.visible(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) if len(zones) == 0 and iimage: imageName = '' if accessible.name and len(accessible.name): imageName = accessible.name elif accessible.description and len(accessible.description): imageName = accessible.description elif iimage.imageDescription and len(iimage.imageDescription): imageName = iimage.imageDescription (x, y) = iimage.getImagePosition(0) (width, height) = iimage.getImageSize() if width != 0 and height != 0 and self.script.visible(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height): clipping = self.clip(x, y, width, height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) if clipping[2] != 0 or clipping[3] != 0: zones.append(Zone(accessible, imageName, clipping[0], clipping[1], clipping[2], clipping[3])) clipping = self.clip(extents.x, extents.y, extents.width, extents.height, cliprect.x, cliprect.y, cliprect.width, cliprect.height) role = accessible.getRole() if len(zones) == 0 and role in [ pyatspi.ROLE_SCROLL_BAR, pyatspi.ROLE_SLIDER, pyatspi.ROLE_PROGRESS_BAR]: zones.append(ValueZone(accessible, clipping[0], clipping[1], clipping[2], clipping[3])) elif role != pyatspi.ROLE_COMBO_BOX and role != pyatspi.ROLE_EMBEDDED and role != pyatspi.ROLE_LABEL and role != pyatspi.ROLE_MENU and role != pyatspi.ROLE_PAGE_TAB and accessible.childCount > 0: pass elif len(zones) == 0: string = '' if role == pyatspi.ROLE_COMBO_BOX: try: selection = accessible[0].querySelection() except: string = self.script.getDisplayedText(accessible[0]) item = selection.getSelectedChild(0) if item: string = item.name if not string and accessible.name and len(accessible.name): string = accessible.name elif accessible.description and len(accessible.description): string = accessible.description if string == '' and role != pyatspi.ROLE_TABLE_CELL: string = accessible.getRoleName() if len(string): pass None if clipping[2] != 0 or clipping[3] != 0 else clipping[3] != 0 self._insertStateZone(zones, accessible) return zones def getShowingZones(self, root): """Returns a list of all interesting, non-intersecting, regions that are drawn on the screen. Each element of the list is the Accessible object associated with a given region. The term 'zone' here is inherited from OCR algorithms and techniques. The Zones are returned in no particular order. Arguments: - root: the Accessible object to traverse Returns: a list of Zones under the specified object """ if not root: return [] zones = [] try: rootexts = root.queryComponent().getExtents(0) except: root return [] rootrole = root.getRole() if root.childCount <= 0: return self.getZonesFromAccessible(root, rootexts) stateset = root.getState() if (root.parent or root.parent.getRole() == pyatspi.ROLE_MENU_BAR) and rootrole == pyatspi.ROLE_COMBO_BOX and rootrole == pyatspi.ROLE_EMBEDDED and rootrole == pyatspi.ROLE_TEXT or rootrole == pyatspi.ROLE_SCROLL_BAR: return self.getZonesFromAccessible(root, rootexts) if rootrole == pyatspi.ROLE_PAGE_TAB: zones.extend(self.getZonesFromAccessible(root, rootexts)) try: root.queryText() if len(zones) == 0: zones = self.getZonesFromAccessible(root, rootexts) except NotImplementedError: pass showingDescendants = self.script.getShowingDescendants(root) if len(showingDescendants): for child in showingDescendants: zones.extend(self.getShowingZones(child)) else: for i in range(0, root.childCount): child = root.getChildAtIndex(i) if child == root: debug.println(debug.LEVEL_WARNING, 'flat_review.getShowingZones: ' + 'WARNING CHILD == PARENT!!!') continue elif not child: debug.println(debug.LEVEL_WARNING, 'flat_review.getShowingZones: ' + 'WARNING CHILD IS NONE!!!') continue elif child.parent != root: debug.println(debug.LEVEL_WARNING, 'flat_review.getShowingZones: ' + 'WARNING CHILD.PARENT != PARENT!!!') if self.script.pursueForFlatReview(child): zones.extend(self.getShowingZones(child)) continue return zones def clusterZonesByLine(self, zones): '''Given a list of interesting accessible objects (the Zones), returns a list of lines in order from the top to bottom, where each line is a list of accessible objects in order from left to right. ''' if len(zones) == 0: return [] numZones = len(zones) for i in range(0, numZones): for j in range(0, numZones - 1 - i): a = zones[j] b = zones[j + 1] if b.y < a.y: zones[j] = b zones[j + 1] = a continue len(zones) == 0 lineClusters = [] for clusterCandidate in zones: addedToCluster = False for lineCluster in lineClusters: inCluster = True for zone in lineCluster: if not zone.onSameLine(clusterCandidate): inCluster = False break continue if inCluster: i = 0 while i < len(lineCluster): zone = lineCluster[i] if clusterCandidate.x < zone.x: break continue i += 1 lineCluster.insert(i, clusterCandidate) addedToCluster = True break continue if not addedToCluster: lineClusters.append([ clusterCandidate]) continue lines = [] lineIndex = 0 for lineCluster in lineClusters: lines.append(Line(lineIndex, lineCluster)) zoneIndex = 0 for zone in lineCluster: zone.line = lines[lineIndex] zone.index = zoneIndex zoneIndex += 1 lineIndex += 1 return lines def _dumpCurrentState(self): print 'line=%d, zone=%d, word=%d, char=%d' % (self.lineIndex, self.zoneIndex, self.wordIndex, self.zoneIndex) zone = self.lines[self.lineIndex].zones[self.zoneIndex] text = zone.accessible.queryText() if not text: print ' Not Accessibility_Text' return None print ' getTextBeforeOffset: %d' % text.caretOffset (string, startOffset, endOffset) = text.getTextBeforeOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START) print " WORD_START: start=%d end=%d string='%s'" % (startOffset, endOffset, string) (string, startOffset, endOffset) = text.getTextBeforeOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_END) print " WORD_END: start=%d end=%d string='%s'" % (startOffset, endOffset, string) print ' getTextAtOffset: %d' % text.caretOffset (string, startOffset, endOffset) = text.getTextAtOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START) print " WORD_START: start=%d end=%d string='%s'" % (startOffset, endOffset, string) (string, startOffset, endOffset) = text.getTextAtOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_END) print " WORD_END: start=%d end=%d string='%s'" % (startOffset, endOffset, string) print ' getTextAfterOffset: %d' % text.caretOffset (string, startOffset, endOffset) = text.getTextAfterOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START) print " WORD_START: start=%d end=%d string='%s'" % (startOffset, endOffset, string) (string, startOffset, endOffset) = text.getTextAfterOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_END) print " WORD_END: start=%d end=%d string='%s'" % (startOffset, endOffset, string) def setCurrent(self, lineIndex, zoneIndex, wordIndex, charIndex): '''Sets the current character of interest. Arguments: - lineIndex: index into lines - zoneIndex: index into lines[lineIndex].zones - wordIndex: index into lines[lineIndex].zones[zoneIndex].words - charIndex: index lines[lineIndex].zones[zoneIndex].words[wordIndex].chars ''' self.lineIndex = lineIndex self.zoneIndex = zoneIndex self.wordIndex = wordIndex self.charIndex = charIndex self.targetCharInfo = self.getCurrent(Context.CHAR) def clickCurrent(self, button = 1): '''Performs a mouse click on the current accessible.''' if not (self.lines) or not (self.lines[self.lineIndex].zones): return None (string, x, y, width, height) = self.getCurrent(Context.CHAR) try: x = max(x, x + width / 2 - 1) eventsynthesizer.clickPoint(x, y + height / 2, button) except: not (self.lines[self.lineIndex].zones) debug.printException(debug.LEVEL_SEVERE) def getCurrentAccessible(self): '''Returns the accessible associated with the current locus of interest. ''' if not (self.lines) or not (self.lines[self.lineIndex].zones): return [ None, -1, -1, -1, -1] zone = self.lines[self.lineIndex].zones[self.zoneIndex] return zone.accessible def getCurrent(self, flatReviewType = ZONE): '''Gets the string, offset, and extent information for the current locus of interest. Arguments: - flatReviewType: one of ZONE, CHAR, WORD, LINE Returns: [string, x, y, width, height] ''' if not (self.lines) or not (self.lines[self.lineIndex].zones): return [ None, -1, -1, -1, -1] zone = self.lines[self.lineIndex].zones[self.zoneIndex] if flatReviewType == Context.ZONE: return [ zone.string, zone.x, zone.y, zone.width, zone.height] if flatReviewType == Context.CHAR: return self.getCurrent(Context.ZONE) if flatReviewType == Context.WORD: return self.getCurrent(Context.ZONE) if flatReviewType == Context.LINE: line = self.lines[self.lineIndex] return [ line.string, line.x, line.y, line.width, line.height] raise Exception('Invalid type: %d' % flatReviewType) def getCurrentBrailleRegions(self): '''Gets the braille for the entire current line. Returns [regions, regionWithFocus] ''' if not (self.lines) or not (self.lines[self.lineIndex].zones): return [ None, None] regionWithFocus = None line = self.lines[self.lineIndex] regions = line.getBrailleRegions() for zone in line.zones: if zone.index == self.zoneIndex: regionWithFocus = zone.brailleRegion regionWithFocus.cursorOffset = 0 regionWithFocus.cursorOffset += self.charIndex regionWithFocus.repositionCursor() break continue regionWithFocus return [ regions, regionWithFocus] def goBegin(self, flatReviewType = WINDOW): """Moves this context's locus of interest to the first char of the first relevant zone. Arguments: - flatReviewType: one of ZONE, LINE or WINDOW Returns True if the locus of interest actually changed. """ if flatReviewType == Context.LINE or flatReviewType == Context.ZONE: lineIndex = self.lineIndex elif flatReviewType == Context.WINDOW: lineIndex = 0 else: raise Exception('Invalid type: %d' % flatReviewType) if flatReviewType == Context.ZONE == Context.ZONE: zoneIndex = self.zoneIndex else: zoneIndex = 0 wordIndex = 0 charIndex = 0 if not self.lineIndex != lineIndex and self.zoneIndex != zoneIndex and self.wordIndex != wordIndex: pass moved = self.charIndex != charIndex if moved: self.lineIndex = lineIndex self.zoneIndex = zoneIndex self.wordIndex = wordIndex self.charIndex = charIndex self.targetCharInfo = self.getCurrent(Context.CHAR) return moved def goEnd(self, flatReviewType = WINDOW): """Moves this context's locus of interest to the last char of the last relevant zone. Arguments: - flatReviewType: one of ZONE, LINE, or WINDOW Returns True if the locus of interest actually changed. """ if flatReviewType == Context.LINE or flatReviewType == Context.ZONE: lineIndex = self.lineIndex elif flatReviewType == Context.WINDOW: lineIndex = len(self.lines) - 1 else: raise Exception('Invalid type: %d' % flatReviewType) if flatReviewType == Context.ZONE == Context.ZONE: zoneIndex = self.zoneIndex else: zoneIndex = len(self.lines[lineIndex].zones) - 1 zone = self.lines[lineIndex].zones[zoneIndex] if zone.words: wordIndex = len(zone.words) - 1 chars = zone.words[wordIndex].chars if chars: charIndex = len(chars) - 1 else: charIndex = 0 else: wordIndex = 0 charIndex = 0 if not self.lineIndex != lineIndex and self.zoneIndex != zoneIndex and self.wordIndex != wordIndex: pass moved = self.charIndex != charIndex if moved: self.lineIndex = lineIndex self.zoneIndex = zoneIndex self.wordIndex = wordIndex self.charIndex = charIndex self.targetCharInfo = self.getCurrent(Context.CHAR) return moved def goPrevious(self, flatReviewType = ZONE, wrap = WRAP_ALL, omitWhitespace = True): """Moves this context's locus of interest to the first char of the previous type. Arguments: - flatReviewType: one of ZONE, CHAR, WORD, LINE - wrap: if True, will cross boundaries, including top and bottom; if False, will stop on boundaries. Returns True if the locus of interest actually changed. """ moved = False if flatReviewType == Context.ZONE: if self.zoneIndex > 0: self.zoneIndex -= 1 self.wordIndex = 0 self.charIndex = 0 moved = True elif wrap & Context.WRAP_LINE: if self.lineIndex > 0: self.lineIndex -= 1 self.zoneIndex = len(self.lines[self.lineIndex].zones) - 1 self.wordIndex = 0 self.charIndex = 0 moved = True elif wrap & Context.WRAP_TOP_BOTTOM: self.lineIndex = len(self.lines) - 1 self.zoneIndex = len(self.lines[self.lineIndex].zones) - 1 self.wordIndex = 0 self.charIndex = 0 moved = True elif flatReviewType == Context.CHAR: if self.charIndex > 0: self.charIndex -= 1 moved = True else: moved = self.goPrevious(Context.WORD, wrap, False) if moved: zone = self.lines[self.lineIndex].zones[self.zoneIndex] if zone.words: chars = zone.words[self.wordIndex].chars if chars: self.charIndex = len(chars) - 1 elif flatReviewType == Context.WORD: zone = self.lines[self.lineIndex].zones[self.zoneIndex] accessible = zone.accessible lineIndex = self.lineIndex zoneIndex = self.zoneIndex wordIndex = self.wordIndex charIndex = self.charIndex if self.wordIndex > 0: self.wordIndex -= 1 self.charIndex = 0 moved = True else: moved = self.goPrevious(Context.ZONE, wrap) if moved: zone = self.lines[self.lineIndex].zones[self.zoneIndex] if zone.words: self.wordIndex = len(zone.words) - 1 zone = self.lines[self.lineIndex].zones[self.zoneIndex] if omitWhitespace and moved: if (len(zone.string) == 0 or len(zone.words)) and zone.words[self.wordIndex].string.isspace(): if zone.accessible == accessible: moved = self.goPrevious(Context.WORD, wrap) else: wordIndex = self.wordIndex - 1 while wordIndex >= 0: if not (zone.words[wordIndex].string) and not len(zone.words[wordIndex].string) or zone.words[wordIndex].string.isspace(): wordIndex -= 1 continue break if wordIndex >= 0: self.wordIndex = wordIndex if not moved: self.lineIndex = lineIndex self.zoneIndex = zoneIndex self.wordIndex = wordIndex self.charIndex = charIndex elif flatReviewType == Context.LINE: if wrap & Context.WRAP_LINE: if self.lineIndex > 0: self.lineIndex -= 1 self.zoneIndex = 0 self.wordIndex = 0 self.charIndex = 0 moved = True elif wrap & Context.WRAP_TOP_BOTTOM and len(self.lines) != 1: self.lineIndex = len(self.lines) - 1 self.zoneIndex = 0 self.wordIndex = 0 self.charIndex = 0 moved = True else: raise Exception('Invalid type: %d' % flatReviewType) if flatReviewType == Context.ZONE and flatReviewType != Context.LINE: self.targetCharInfo = self.getCurrent(Context.CHAR) return moved def goNext(self, flatReviewType = ZONE, wrap = WRAP_ALL, omitWhitespace = True): """Moves this context's locus of interest to first char of the next type. Arguments: - flatReviewType: one of ZONE, CHAR, WORD, LINE - wrap: if True, will cross boundaries, including top and bottom; if False, will stop on boundaries. """ moved = False if flatReviewType == Context.ZONE: if self.zoneIndex < len(self.lines[self.lineIndex].zones) - 1: self.zoneIndex += 1 self.wordIndex = 0 self.charIndex = 0 moved = True elif wrap & Context.WRAP_LINE: if self.lineIndex < len(self.lines) - 1: self.lineIndex += 1 self.zoneIndex = 0 self.wordIndex = 0 self.charIndex = 0 moved = True elif wrap & Context.WRAP_TOP_BOTTOM: self.lineIndex = 0 self.zoneIndex = 0 self.wordIndex = 0 self.charIndex = 0 moved = True elif flatReviewType == Context.CHAR: zone = self.lines[self.lineIndex].zones[self.zoneIndex] if zone.words: chars = zone.words[self.wordIndex].chars if chars: if self.charIndex < len(chars) - 1: self.charIndex += 1 moved = True else: moved = self.goNext(Context.WORD, wrap, False) else: moved = self.goNext(Context.WORD, wrap) else: moved = self.goNext(Context.ZONE, wrap) elif flatReviewType == Context.WORD: zone = self.lines[self.lineIndex].zones[self.zoneIndex] accessible = zone.accessible lineIndex = self.lineIndex zoneIndex = self.zoneIndex wordIndex = self.wordIndex charIndex = self.charIndex if zone.words: if self.wordIndex < len(zone.words) - 1: self.wordIndex += 1 self.charIndex = 0 moved = True else: moved = self.goNext(Context.ZONE, wrap) else: moved = self.goNext(Context.ZONE, wrap) zone = self.lines[self.lineIndex].zones[self.zoneIndex] if omitWhitespace and moved: if (len(zone.string) == 0 or len(zone.words)) and zone.words[self.wordIndex].string.isspace(): if zone.accessible == accessible: moved = self.goNext(Context.WORD, wrap) else: wordIndex = self.wordIndex + 1 while wordIndex < len(zone.words): if not (zone.words[wordIndex].string) and not len(zone.words[wordIndex].string) or zone.words[wordIndex].string.isspace(): wordIndex += 1 continue break if wordIndex < len(zone.words): self.wordIndex = wordIndex if not moved: self.lineIndex = lineIndex self.zoneIndex = zoneIndex self.wordIndex = wordIndex self.charIndex = charIndex elif flatReviewType == Context.LINE: if wrap & Context.WRAP_LINE: if self.lineIndex < len(self.lines) - 1: self.lineIndex += 1 self.zoneIndex = 0 self.wordIndex = 0 self.charIndex = 0 moved = True elif wrap & Context.WRAP_TOP_BOTTOM and self.lineIndex != 0: self.lineIndex = 0 self.zoneIndex = 0 self.wordIndex = 0 self.charIndex = 0 moved = True else: raise Exception('Invalid type: %d' % flatReviewType) if flatReviewType == Context.ZONE and flatReviewType != Context.LINE: self.targetCharInfo = self.getCurrent(Context.CHAR) return moved def goAbove(self, flatReviewType = LINE, wrap = WRAP_ALL): """Moves this context's locus of interest to first char of the type that's closest to and above the current locus of interest. Arguments: - flatReviewType: LINE - wrap: if True, will cross top/bottom boundaries; if False, will stop on top/bottom boundaries. Returns: [string, startOffset, endOffset, x, y, width, height] """ moved = False if flatReviewType == Context.CHAR: if not self.targetCharInfo: self.targetCharInfo = self.getCurrent(Context.CHAR) target = self.targetCharInfo (string, x, y, width, height) = target middleTargetX = x + width / 2 moved = self.goPrevious(Context.LINE, wrap) if moved: while True: (string, bx, by, bwidth, bheight) = self.getCurrent(Context.CHAR) if bx + width >= middleTargetX: break continue if not self.goNext(Context.CHAR, Context.WRAP_NONE): break continue self.targetCharInfo = target elif flatReviewType == Context.LINE: return self.goPrevious(flatReviewType, wrap) raise Exception('Invalid type: %d' % flatReviewType) return moved def goBelow(self, flatReviewType = LINE, wrap = WRAP_ALL): """Moves this context's locus of interest to the first char of the type that's closest to and below the current locus of interest. Arguments: - flatReviewType: one of WORD, LINE - wrap: if True, will cross top/bottom boundaries; if False, will stop on top/bottom boundaries. Returns: [string, startOffset, endOffset, x, y, width, height] """ moved = False if flatReviewType == Context.CHAR: if not self.targetCharInfo: self.targetCharInfo = self.getCurrent(Context.CHAR) target = self.targetCharInfo (string, x, y, width, height) = target middleTargetX = x + width / 2 moved = self.goNext(Context.LINE, wrap) if moved: while True: (string, bx, by, bwidth, bheight) = self.getCurrent(Context.CHAR) if bx + width >= middleTargetX: break continue if not self.goNext(Context.CHAR, Context.WRAP_NONE): break continue self.targetCharInfo = target elif flatReviewType == Context.LINE: moved = self.goNext(flatReviewType, wrap) else: raise Exception('Invalid type: %d' % flatReviewType) return flatReviewType == Context.CHAR